package com.sky.service;

import com.sky.entity.Node;
import com.sky.entity.PathResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@Service
public class RouteService {

    // 注入高德地图API密钥
    @Value("${amap.api.key}")
    private String apiKey;

    /**
     * 获取从起点到终点的路径信息，并使用A*算法优化路径。
     *
     * @param start 起点坐标，格式为 "经度,纬度"
     * @param end   终点坐标，格式为 "经度,纬度"
     * @return 包含路径信息的PathResponse对象
     */
    public PathResponse getRoute(String start, String end) {
        RestTemplate restTemplate = new RestTemplate();
        String url = String.format("https://restapi.amap.com/v3/direction/driving?origin=%s&destination=%s&key=%s", start, end, apiKey);
        try {
            PathResponse response = restTemplate.getForObject(url, PathResponse.class);
            if (response != null && "1".equals(response.getStatus())) {
                // 解析路径节点
                List<Node> pathNodes = parsePath(response.getRoute().getPaths().get(0).getSteps());
                // 使用A*算法优化路径
                List<Node> optimizedPath = aStarAlgorithm(pathNodes.get(0), pathNodes.get(pathNodes.size() - 1));
                // 将优化后的路径节点转换为字符串列表
                response.getRoute().setOptimizedPath(convertNodesToPoints(optimizedPath));
            }
            return response;
        } catch (Exception e) {
            System.err.println("Error fetching route: " + e.getMessage());
            return new PathResponse();
        }
    }

    /**
     * 解析路径步骤中的节点信息。
     *
     * @param steps 路径步骤列表
     * @return 节点列表
     */
    private List<Node> parsePath(List<PathResponse.Step> steps) {
        List<Node> nodes = new ArrayList<>();
        for (PathResponse.Step step : steps) {
            // 按照";"分割多段线
            String[] points = step.getPolyline().split(";");
            for (String point : points) {
                // 按照","分割经纬度
                String[] coordinates = point.split(",");
                if (coordinates.length == 2) {
                    double x = Double.parseDouble(coordinates[0]);
                    double y = Double.parseDouble(coordinates[1]);
                    nodes.add(new Node(x, y));
                }
            }
        }
        return nodes;
    }

    /**
     * 使用A*算法找到从起点到终点的最优路径。
     *
     * @param startNode 起点节点
     * @param endNode   终点节点
     * @return 最优路径上的节点列表
     */
    private List<Node> aStarAlgorithm(Node startNode, Node endNode) {
        // 开放集：存储待评估的节点
        Set<Node> openSet = new HashSet<>();
        // 闭合集：存储已评估过的节点
        Set<Node> closedSet = new HashSet<>();

        // 将起始节点加入开放集
        openSet.add(startNode);

        // 当开放集中还有待处理的节点时，继续循环
        while (!openSet.isEmpty()) {
            // 从开放集中找到F值最低的节点作为当前节点
            Node currentNode = getNodeWithLowestFCost(openSet);

            // 如果当前节点是目标节点，则重建路径并返回
            if (currentNode.equals(endNode)) {
                return reconstructPath(currentNode);
            }

            // 将当前节点从开放集中移除，并加入闭合集
            openSet.remove(currentNode);
            closedSet.add(currentNode);

            // 获取当前节点的所有邻居节点
            for (Node neighbor : getNeighbors(currentNode)) {
                // 如果邻居节点已经在闭合集中，跳过该节点
                if (closedSet.contains(neighbor)) continue;

                // 计算从起始节点经过当前节点到达邻居节点的G值（实际代价）
                double tentativeGCost = currentNode.getgCost() + calculateDistance(currentNode, neighbor);

                // 如果新的G值小于邻居节点的现有G值，或者邻居节点不在开放集中
                if (tentativeGCost < neighbor.getgCost() || !openSet.contains(neighbor)) {
                    // 更新邻居节点的父节点为当前节点
                    neighbor.setParent(currentNode);
                    // 更新邻居节点的G值
                    neighbor.setgCost(tentativeGCost);
                    // 更新邻居节点的H值（启发式代价）
                    neighbor.sethCost(calculateHeuristic(neighbor, endNode));

                    // 如果邻居节点不在开放集中，将其加入开放集
                    if (!openSet.contains(neighbor)) {
                        openSet.add(neighbor);
                    }
                }
            }
        }

        // 如果找不到路径，返回空列表
        return new ArrayList<>(); // No path found
    }

    /**
     * 在开放集中找到F值最低的节点。
     *
     * @param openSet 开放集
     * @return F值最低的节点
     */
    private Node getNodeWithLowestFCost(Set<Node> openSet) {
        Node lowestFCostNode = null;
        for (Node node : openSet) {
            if (lowestFCostNode == null || node.getfCost() < lowestFCostNode.getfCost()) {
                lowestFCostNode = node;
            }
        }
        return lowestFCostNode;
    }

    /**
     * 获取当前节点的所有邻居节点。
     *
     * @param node 当前节点
     * @return 邻居节点列表
     */
    private List<Node> getNeighbors(Node node) {
        // 简单情况下假设邻居是列表中相邻的节点
        return Collections.emptyList();
    }

    /**
     * 计算两个节点之间的欧几里得距离。
     *
     * @param nodeA 第一个节点
     * @param nodeB 第二个节点
     * @return 两个节点之间的距离
     */
    private double calculateDistance(Node nodeA, Node nodeB) {
        double dx = nodeA.getX() - nodeB.getX();
        double dy = nodeA.getY() - nodeB.getY();
        return Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * 计算启发式函数值（这里使用欧几里得距离）。
     *
     * @param node    当前节点
     * @param endNode 目标节点
     * @return 启发式函数值
     */
    private double calculateHeuristic(Node node, Node endNode) {
        return calculateDistance(node, endNode);
    }

    /**
     * 将节点列表转换为坐标点字符串列表。
     *
     * @param nodes 节点列表
     * @return 坐标点字符串列表
     */
    private List<String> convertNodesToPoints(List<Node> nodes) {
        List<String> points = new ArrayList<>();
        for (Node node : nodes) {
            points.add(node.getX() + "," + node.getY());
        }
        return points;
    }

    /**
     * 根据父节点重建路径。
     *
     * @param currentNode 当前节点
     * @return 完整路径上的节点列表
     */
    private List<Node> reconstructPath(Node currentNode) {
        List<Node> path = new ArrayList<>();
        while (currentNode != null) {
            path.add(currentNode);
            currentNode = currentNode.getParent();
        }
        Collections.reverse(path);
        return path;
    }
}